1 /*
2 Copyright: Marcelo S. N. Mancini (Hipreme|MrcSnm), 2018 - 2021
3 License:   [https://creativecommons.org/licenses/by/4.0/|CC BY-4.0 License].
4 Authors: Marcelo S. N. Mancini
5 
6 	Copyright Marcelo S. N. Mancini 2018 - 2021.
7 Distributed under the CC BY-4.0 License.
8    (See accompanying file LICENSE.txt or copy at
9 	https://creativecommons.org/licenses/by/4.0/
10 */
11 module hip.hiprenderer.shader.shader;
12 public import hip.hiprenderer.shader.shadervar :
13 ShaderHint, ShaderVariablesLayout, ShaderVar;
14 
15 import hip.api.data.commons:IReloadable;
16 import hip.api.renderer.texture;
17 import hip.math.matrix;
18 import hip.error.handler;
19 import hip.hiprenderer.shader.shadervar;
20 import hip.hiprenderer.backend.gl.glshader;
21 import hip.hiprenderer.shader;
22 import hip.hiprenderer.renderer;
23 import hip.util.file;
24 import hip.util.string:indexOf;
25 
26 enum ShaderStatus
27 {
28     SUCCESS,
29     VERTEX_COMPILATION_ERROR,
30     FRAGMENT_COMPILATION_ERROR,
31     LINK_ERROR,
32     UNKNOWN_ERROR
33 }
34 
35 enum ShaderTypes
36 {
37     VERTEX,
38     FRAGMENT,
39     GEOMETRY, //Unsupported yet
40     NONE 
41 }
42 
43 enum HipShaderPresets
44 {
45     DEFAULT,
46     FRAME_BUFFER,
47     GEOMETRY_BATCH,
48     SPRITE_BATCH,
49     BITMAP_TEXT,
50     NONE
51 }
52 
53 /** 
54  * This interface is currrently a Shader factory.
55  */
56 interface IShader
57 {
58     VertexShader createVertexShader();
59     FragmentShader createFragmentShader();
60     ShaderProgram createShaderProgram();
61 
62     bool compileShader(FragmentShader fs, string shaderSource);
63     bool compileShader(VertexShader vs, string shaderSource);
64     bool linkProgram(ref ShaderProgram program, VertexShader vs,  FragmentShader fs);
65 
66     void setBlending(ShaderProgram prog, HipBlendFunction src, HipBlendFunction dst, HipBlendEquation eq);
67     void bind(ShaderProgram program);
68     void unbind(ShaderProgram program);
69     void sendVertexAttribute(uint layoutIndex, int valueAmount, uint dataType, bool normalize, uint stride, int offset);
70     int  getId(ref ShaderProgram prog, string name);
71     
72 
73     ///Used as intermediary for deleting non program intermediary in opengl
74     void deleteShader(FragmentShader* fs);
75     ///Used as intermediary for deleting non program intermediary in opengl
76     void deleteShader(VertexShader* vs);
77 
78     void createVariablesBlock(ref ShaderVariablesLayout layout);
79     bool setShaderVar(ShaderVar* sv, ShaderProgram prog, void* value);
80     void sendVars(ref ShaderProgram prog, ShaderVariablesLayout[string] layouts);
81 
82     /** 
83      * Each graphics API has its own way to bind array of textures, thus, this version was required.
84      */
85     void bindArrayOfTextures(ref ShaderProgram prog, IHipTexture[] textures, string varName);
86     void dispose(ref ShaderProgram);
87 
88     void onRenderFrameEnd(ShaderProgram program);
89 }
90 
91 abstract class VertexShader
92 {
93     abstract string getDefaultVertex();
94     abstract string getFrameBufferVertex();
95     abstract string getGeometryBatchVertex();
96     abstract string getSpriteBatchVertex();
97     abstract string getBitmapTextVertex();
98 }
99 abstract class FragmentShader
100 {
101     abstract string getDefaultFragment();
102     abstract string getFrameBufferFragment();
103     abstract string getGeometryBatchFragment();
104     abstract string getSpriteBatchFragment();
105     abstract string getBitmapTextFragment();
106 }
107 
108 abstract class ShaderProgram
109 {
110     string name;
111 }
112 
113 
114 public class Shader : IReloadable
115 {
116     VertexShader vertexShader;
117     FragmentShader fragmentShader;
118     ShaderProgram shaderProgram;
119     ShaderVariablesLayout[string] layouts;
120     protected ShaderVariablesLayout defaultLayout;
121     //Optional
122     IShader shaderImpl;
123     string fragmentShaderPath;
124     string vertexShaderPath;
125 
126     protected string internalVertexSource;
127     protected string internalFragmentSource;
128     private bool isUseCall = false;
129 
130     this(IShader shaderImpl)
131     {
132         this.shaderImpl = shaderImpl;
133         vertexShader = shaderImpl.createVertexShader();
134         fragmentShader = shaderImpl.createFragmentShader();
135         shaderProgram = shaderImpl.createShaderProgram();
136     }
137     this(IShader shaderImpl, string vertexSource, string fragmentSource)
138     {
139         this(shaderImpl);
140         ShaderStatus status = loadShaders(vertexSource, fragmentSource);
141         if(status != ShaderStatus.SUCCESS)
142         {
143             import hip.console.log;
144             logln("Failed loading shaders");
145         }
146     }
147 
148     void setFromPreset(HipShaderPresets preset = HipShaderPresets.DEFAULT)
149     {
150         ShaderStatus status = ShaderStatus.SUCCESS;
151         fragmentShaderPath="hip.hiprenderer.backend.";
152         switch(HipRenderer.getRendererType())
153         {
154             case HipRendererType.D3D11:
155                 fragmentShaderPath~= "d3d.shader";
156                 break;
157             case HipRendererType.GL3:
158                 fragmentShaderPath~= "gl3.shader";
159                 break;
160             case HipRendererType.METAL:
161                 fragmentShaderPath~= "metal.shader";
162                 break;
163             default:break;
164         }
165 
166         switch(preset) with(HipShaderPresets)
167         {
168             case SPRITE_BATCH:
169                 fragmentShaderPath~= ".SPRITE_BATCH";
170                 status = loadShaders(vertexShader.getSpriteBatchVertex(), fragmentShader.getSpriteBatchFragment(), fragmentShaderPath);
171                 break;
172             case FRAME_BUFFER:
173                 fragmentShaderPath~= ".FRAME_BUFFER";
174                 status = loadShaders(vertexShader.getFrameBufferVertex(), fragmentShader.getFrameBufferFragment(), fragmentShaderPath);
175                 break;
176             case GEOMETRY_BATCH:
177                 fragmentShaderPath~= ".GEOMETRY_BATCH";
178                 status = loadShaders(vertexShader.getGeometryBatchVertex(), fragmentShader.getGeometryBatchFragment(), fragmentShaderPath);
179                 break;
180             case BITMAP_TEXT:
181                 fragmentShaderPath~= ".BITMAP_TEXT";
182                 status = loadShaders(vertexShader.getBitmapTextVertex(), fragmentShader.getBitmapTextFragment(), fragmentShaderPath);
183                 break;
184             case DEFAULT:
185                 fragmentShaderPath~= ".DEFAULT";
186                 status = loadShaders(vertexShader.getDefaultVertex(),fragmentShader.getDefaultFragment(), fragmentShaderPath);
187                 break;
188             case NONE:
189             default:
190                 break;
191         }
192         vertexShaderPath = fragmentShaderPath;
193         
194         if(status != ShaderStatus.SUCCESS)
195         {
196             import hip.console.log;
197             logln("Failed loading shaders with status ", status, " at preset ", preset, " on "~fragmentShaderPath);
198         }
199     }
200 
201     ShaderStatus loadShaders(string vertexShaderSource, string fragmentShaderSource, string shaderPath = "")
202     {
203         this.internalVertexSource = vertexShaderSource;
204         this.internalFragmentSource = fragmentShaderSource;
205 
206         shaderProgram.name = shaderPath;
207         if(!shaderImpl.compileShader(vertexShader, vertexShaderSource))
208             return ShaderStatus.VERTEX_COMPILATION_ERROR;
209         if(!shaderImpl.compileShader(fragmentShader, fragmentShaderSource))
210             return ShaderStatus.FRAGMENT_COMPILATION_ERROR;
211         if(!shaderImpl.linkProgram(shaderProgram, vertexShader, fragmentShader))
212             return ShaderStatus.LINK_ERROR;
213         // deleteShaders();
214         return ShaderStatus.SUCCESS;
215     }
216 
217     ShaderStatus loadShadersFromFiles(string vertexShaderPath, string fragmentShaderPath)
218     {
219         this.vertexShaderPath = vertexShaderPath;
220         this.fragmentShaderPath = fragmentShaderPath;
221         return loadShaders(getFileContent(vertexShaderPath), getFileContent(fragmentShaderPath));
222     }
223 
224     ShaderStatus reloadShaders()
225     {
226         return loadShadersFromFiles(this.vertexShaderPath, this.fragmentShaderPath);
227     }
228 
229     void setVertexAttribute(uint layoutIndex, int valueAmount, uint dataType, bool normalize, uint stride, int offset)
230     {
231         shaderImpl.sendVertexAttribute(layoutIndex, valueAmount, dataType, normalize, stride, offset);
232     }
233 
234 
235     /** 
236      * If validateData is true, it will compare if the data has changed for choosing whether it should or not
237      send to the GPU.
238      * Params:
239      *   name = 
240      *   val = 
241      *   validateData = 
242      */
243     public void setVertexVar(T)(string name, T val, bool validateData = false)
244     {
245         ShaderVar* v = tryGetShaderVar(name, ShaderTypes.VERTEX);
246         if(v != null)
247         {
248             v.set(val, validateData);
249         }
250     }
251 
252     private ShaderVar* tryGetShaderVar(string name, ShaderTypes type)
253     {
254         import hip.util.conv:to;
255         bool isUnused;
256         ShaderVar* v = findByName(name, isUnused);
257         if(v is null)
258         {
259             if(!isUnused)
260                 ErrorHandler.showWarningMessage("Shader " ~ type.to!string ~ " Var not set on shader loaded from '"~fragmentShaderPath~"'",
261                 "Could not find shader var with name "~name~
262                 ((layouts.length == 0) ?". Did you forget to addVarLayout on the shader?" :
263                 " Did you forget to add a layout namespace to the var name?"
264                 ));
265             return null;
266         }
267         if(v.shaderType != type)
268         {
269             import hip.console.log;
270             logln = v.shaderType;
271             ErrorHandler.assertExit(false, "Variable named "~name~" must be from " ~ type.to!string ~ " Shader");
272         }
273         return v;
274     }
275     /** 
276      * If validateData is true, it will compare if the data has changed for choosing whether it should or not
277      send to the GPU.
278      * Params:
279      *   name = 
280      *   val = 
281      *   validateData = 
282      */
283     public void setFragmentVar(T)(string name, T val, bool validateData = false)
284     {
285         ShaderVar* v = tryGetShaderVar(name, ShaderTypes.FRAGMENT);
286         if(v != null)
287         {
288             if(v.isBlackboxed)
289             {
290                 if(shaderImpl.setShaderVar(v,shaderProgram, cast(void*)&val))
291                     v.isDirty = true;
292             }
293             else
294                 v.set(val, validateData);
295         }
296     }
297 
298     protected ShaderVar* findByName(string name, out bool isUnused) @nogc
299     {
300         int accessorSeparatorIndex = name.indexOf(".");
301 
302         bool isDefault = accessorSeparatorIndex == -1;
303         if(isDefault)
304         {
305             ShaderVarLayout* sL = name in defaultLayout.variables;
306             if(sL !is null)
307                 return sL.sVar;
308             isUnused = defaultLayout.isUnused(name);
309         }
310         else
311         {
312             ShaderVariablesLayout* l = (name[0..accessorSeparatorIndex] in layouts);
313             if(l !is null)
314             {
315                 ShaderVarLayout* sL = name[accessorSeparatorIndex+1..$] in l.variables;
316                 if(sL !is null)
317                     return sL.sVar;
318                 isUnused = l.isUnused(name[accessorSeparatorIndex+1..$]);
319             }
320         }
321         return null;
322     }
323     public ShaderVar* get(string name)
324     {
325         bool ignored;
326         return findByName(name, ignored);
327     }
328 
329 
330     public void addVarLayout(ShaderVariablesLayout layout)
331     {
332         ErrorHandler.assertLazyExit((layout.name in layouts) is null, "Shader: VariablesLayout '"~layout.name~"' is already defined");
333         if(defaultLayout is null)
334             defaultLayout = layout;
335         layouts[layout.name] = layout;
336         layout.lock(this);
337         shaderImpl.createVariablesBlock(layout);
338     }
339 
340     /** 
341      * This creates a state in the current shader to which block will be accessed
342      * when using setVertexVar(".property"). If no default block is set ("")
343      * .property will always access the first block defined
344      * Params:
345      *   blockName = Which block will be accessed with .property
346      */
347     public void setDefaultBlock(string blockName){defaultLayout = layouts[blockName];}
348 
349     void bind(){shaderImpl.bind(shaderProgram);}
350     void unbind(){shaderImpl.unbind(shaderProgram);}
351     void setBlending(HipBlendFunction src, HipBlendFunction dest, HipBlendEquation eq)
352     {
353         shaderImpl.setBlending(shaderProgram, src, dest, eq);
354     }
355 
356     auto opDispatch(string member)()
357     {
358         static if(member == "useLayout")
359         {
360             isUseCall = true;
361             return this;
362         }
363         else
364         {
365             if(isUseCall)
366             {
367                 setDefaultBlock(member);
368                 isUseCall = false;
369                 ShaderVar s;
370                 return s;
371             }
372             return *defaultLayout.variables[member].sVar;
373         }
374     }
375     auto opDispatch(string member, T)(T value)
376     {
377         if(!defaultLayout.variables[member].sVar.set(value, false))
378         {
379             ErrorHandler.assertExit(false, "Invalid value of type "~
380             T.stringof~" passed to "~defaultLayout.name~"."~member);
381         }
382     }
383 
384     void sendVars()
385     {
386         shaderImpl.sendVars(shaderProgram, layouts);
387     }
388 
389     /**
390     *  Bind array of textures.
391     *   - This is handled a little different than simply blindly binding *  to each slot.
392     *   Since OpenGL, Direct3D and Metal handles that differently, a more general solution is required.
393     */
394     void bindArrayOfTextures(IHipTexture[] textures, string varName)
395     {
396         shaderImpl.bindArrayOfTextures(shaderProgram, textures, varName);
397     }
398 
399 
400     protected void deleteShaders()
401     {
402         shaderImpl.deleteShader(&fragmentShader);
403         shaderImpl.deleteShader(&vertexShader);
404     }
405     
406     bool reload()
407     {
408         vertexShader = shaderImpl.createVertexShader();
409         fragmentShader = shaderImpl.createFragmentShader();
410         shaderProgram = shaderImpl.createShaderProgram();
411 
412         return loadShaders(internalVertexSource, internalFragmentSource) == ShaderStatus.SUCCESS;
413     }
414     
415     void onRenderFrameEnd()
416     {
417         shaderImpl.onRenderFrameEnd(shaderProgram);
418     }
419 
420 }